<?php

namespace App\Services;


use App\Exceptions\CouponCodeUnavailableException;
use App\Exceptions\InternalException;
use App\Exceptions\InvalidRequestException;
use App\Jobs\CloseOrder;
use App\Models\CouponCode;
use App\Models\Order;
use App\Models\ProductSku;
use App\Models\User;
use App\Models\UserAddress;
use Carbon\Carbon;

class OrderService
{
    /**
     * 创建订单
     *
     * @param  User  $user
     * @param  UserAddress  $address
     * @param $remark
     * @param $items
     * @param  CouponCode|null  $coupon
     * @return mixed
     * @throws CouponCodeUnavailableException
     * @throws \Throwable
     */
    public function store(User $user, UserAddress $address, $remark, $items, CouponCode $coupon = null)
    {
        if ($coupon) {
            $coupon->checkAvailable($user);
        }
        // 开启一个数据库事务
        $order = \DB::transaction(
            function () use ($user, $address, $remark, $items, $coupon) {
                // 更新地址最后使用的时间
                $address->update(['last_used_at' => Carbon::now()]);
                // 创建一个订单
                $order = new Order(
                    [
                        // 将地址信息放入订单中
                        'address' => [
                            'address' => $address->full_address,
                            'zip' => $address->zip,
                            'contact_name' => $address->contact_name,
                            'contact_phone' => $address->contact_phone,
                        ],
                        'remark' => $remark,
                        'total_amount' => 0,
                        'type' => Order::TYPE_NORMAL,
                    ]
                );
                // 将订单关联到当前用户
                $order->user()->associate($user);
                // 写入数据库
                $order->save();

                $totalAmount = 0;
                // 遍历用户提交的 SKU
                foreach ($items as $data) {
                    $sku = ProductSku::query()->find($data['sku_id']);
                    // 创建一个 OrderItem 并直接与当前订单关联
                    $item = $order->items()->make(
                        [
                            'amount' => $data['amount'],
                            'price' => $sku->price,
                        ]
                    );
                    $item->product()->associate($sku->product_id);
                    $item->productSku()->associate($sku);
                    $item->save();
                    $totalAmount += $sku->price * $data['amount'];
                    if ($sku->decreaseStock($data['amount']) <= 0) {
                        throw new InvalidRequestException('该商品库存不足');
                    }
                }
                if ($coupon) {
                    // 订单总金额已经算出来了，检查是否符合优惠券规则
                    $coupon->checkAvailable($user, $totalAmount);
                    // 把订单金额修改为优惠后的金额
                    $totalAmount = $coupon->getAdjustedPrice($totalAmount);
                    // 将订单与优惠券关联
                    $order->couponCode()->associate($coupon);
                    // 增加优惠券使用量，需要判断返回值
                    if ($coupon->changeUsed() <= 0) {
                        throw new CouponCodeUnavailableException('该优惠券已被兑换完');
                    }
                }
                // 更新订单总金额
                $order->update(['total_amount' => $totalAmount]);

                // 将订单的商品从购物车中移除
                $skuIds = collect($items)->pluck('sku_id')->all();
                app(CartService::class)->remove($skuIds);

                return $order;
            }
        );

        // 在这里我们直接用  dispatch 函数
        dispatch(new CloseOrder($order, config('app.order_ttl')));

        return $order;
    }

    /**
     * 众筹商品
     *
     * @param  User  $user
     * @param  UserAddress  $userAddress
     * @param  ProductSku  $sku
     * @param $amount
     * @return mixed
     * @throws \Throwable
     */
    public function crowdfunding(User $user, UserAddress $userAddress, ProductSku $sku, $amount)
    {
        $order = \DB::transaction(
            function () use ($amount, $sku, $user, $userAddress) {
                // 更新地址最后使用时间
                $userAddress->update(['last_used_at' => Carbon::now()]);
                $order = new Order(
                    [
                        'address' => [
                            'address' => $userAddress->fullAddress,
                            'zip' => $userAddress->zip,
                            'contact_name' => $userAddress->contact_name,
                            'contact_phone' => $userAddress->contact_phone,
                        ],
                        'remark' => '',
                        'total_amount' => $sku->price * $amount,
                        'type' => Order::TYPE_CROWDFUNDING,
                    ]
                );
                // 将订单关联当前用户
                $order->user()->associate($user);
                // 写入数据库
                $order->save();
                // 创建一个新的订单并与SKU关联
                $item = $order->items()->make(
                    [
                        'amount' => $amount,
                        'price' => $sku->price,
                    ]
                );
                $item->product()->associate($sku->product_id);
                $item->productSku()->associate($sku);
                $item->save();
                // 减少对于的 SKU 库存
                if ($sku->decreaseStock($amount) <= 0) {
                    throw new InvalidRequestException('该商品库存不足');
                }

                return $order;
            }
        );

        // 众筹结束时间减去当前时间的秒数得到剩余秒数
        $crowfundingTal = $sku->product->crowdfunding->end_at->getTimestamp() - time();
        // 剩余秒数与默认订单关闭时间取较小值作为订单关闭时间
        dispatch(new CloseOrder($order, min(config('app.order_ttl'), $crowfundingTal)));

        return $order;
    }

    /**
     * 处理退款逻辑
     *
     * @param  Order  $order
     * @throws InternalException
     */
    public function refundOrder(Order $order)
    {
        // 判断订单的支付方式
        switch ($order->payment_method) {
            case 'wechat':
                // 生成退款订单号
                $refundNo = Order::getAvailableRefundNo();
                app('wechat_pay')->refund(
                    [
                        'out_trade_no' => $order->no,
                        'total_fee' => $order->total_amount * 100,
                        'refund_fee' => $order->total_amount * 100,
                        'out_refund_no' => $refundNo,
                        'notify_url' => ngrok_url('payment.wechat.refund_notify'),
                    ]
                );
                $order->update(
                    [
                        'refund_no' => $refundNo,
                        'refund_status' => Order::REFUND_STATUS_PROCESSING,
                    ]
                );
                break;
            case 'alipay':
                $refundNo = Order::getAvailableRefundNo();
                $ret = app('alipay')->refund(
                    [
                        'out_trade_no' => $order->no,
                        'refund_amount' => $order->total_amount,
                        'out_request_no' => $refundNo,
                    ]
                );
                if ($ret->sub_code) {
                    $extra = $order->extra;
                    $extra['refund_failed_code'] = $ret->sub_code;
                    $order->update(
                        [
                            'refund_no' => $refundNo,
                            'refund_status' => Order::REFUND_STATUS_FAILED,
                            'extra' => $extra,
                        ]
                    );
                } else {
                    $order->update(
                        [
                            'refund_no' => $refundNo,
                            'refund_status' => Order::REFUND_STATUS_SUCCESS,
                        ]
                    );
                }
                break;
            default:
                throw new InternalException('未知订单支付方式'.$order->payment_method);
                break;
        }
    }
}
